﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Reflection;
using Ionic.Zip;
using System.Drawing;

namespace LegoBrickList
{
    /// <summary>
    /// Helper class for the LDD file.
    /// </summary>
    public class LddFile
    {
        #region Private Variables
        
        private XmlDocument partsDocument;
        private XmlDocument partAndColorCodesDocument;

        #endregion


        #region Public Variables
                
        /// <summary>
        /// Gets or sets the LDD file name.
        /// </summary>
        public string FileName { get; set; }
        /// <summary>
        /// Gets or sets if the LDD file has errors.
        /// </summary>
        public bool HasErrors { get; set; }
        /// <summary>
        /// Gets or sets the LDD file error message.
        /// </summary>
        public string ErrorMessage { get; set; }
        /// <summary>
        /// Gets or sets the file name of the LDD XML file.
        /// </summary>
        public string XmlFileName { get; set; }
        /// <summary>
        /// Gets or sets the file name of the LDD image file.
        /// </summary>
        public string ImageFileName { get; set; }
        /// <summary>
        /// Gets the unique part count.
        /// </summary>
        public int UniquePartCount
        {
            get
            {
                if (LegoParts != null)
                    return LegoParts.Count;
                return 0;
            }
        }
        /// <summary>
        /// Gets the total part count.
        /// </summary>
        public int TotalPartCount
        {
            get
            {
                if (LegoParts != null)
                {
                    int count = 0;
                    foreach (LegoPart part in LegoParts)
                        count += part.Count;
                    return count;
                }
                return 0;
            }
        }
        /// <summary>
        /// Gets the list of Lego parts in the LDD file.
        /// </summary>
        public List<LegoPart> LegoParts { get; private set; }

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes the LDD file class.
        /// </summary>
        public LddFile()
        {
            LegoParts = new List<LegoPart>();
        }

        /// <summary>
        /// Initializes the LDD file class.
        /// </summary>
        /// <param name="fileName">A Lego LDD file name.</param>
        public LddFile(string fileName) 
            : this()
        {
            Load(fileName);
        }

        #endregion

        #region Public Functions
        
        /// <summary>
        /// Loads a Lego LDD file.
        /// </summary>
        /// <param name="fileName"></param>
        public void Load(string fileName)
        {
            if (!File.Exists(fileName))
                throw new FileNotFoundException("LDD file not found", fileName);

            FileName = fileName;
            LegoParts.Clear();
            HasErrors = false;
            ErrorMessage = null;
            XmlFileName = null;
            ImageFileName = null;
            
            ExtractLddFile(fileName);
            if (!HasErrors)
                GetParts(XmlFileName);
        }

        #endregion

        #region Private Functions

        /// <summary>
        /// Gets a list of part numbers and the amount of 
        /// the part in the LDD XML file.
        /// </summary>
        /// <param name="fileName">A LDD XML file name.</param>
        private void GetParts(string fileName)
        {
            try
            {
                //TODO: Change to XML Serializer
                XmlDocument document = new XmlDocument();
                document.Load(fileName);
                XmlNodeList nodes = document.SelectNodes("//Brick");

                foreach (XmlNode node in nodes)
                {
                    LegoPart part = LegoParts.FirstOrDefault(p => p.Number == node.Attributes["itemNos"].Value);
                    if (part == null)
                    {
                        string partNumber = node.Attributes["itemNos"].Value;
                        string partCode;
                        string color;
                        GetPartCodeAndColor(partNumber, out partCode, out color);
                        string description = GetPartDescription(partCode);
                        LegoParts.Add(new LegoPart()
                        {
                            Number = partNumber, 
                            Description = description, 
                            Count = 1,
                            Color = color
                        });
                    }
                    else
                        part.Count++; ;
                }
            }
            catch (XmlException)
            {
                HasErrors = true;
                ErrorMessage = "The part XML file isn't a valid XML file,\r\n" + fileName;
            }
        }
        /// <summary>
        /// Gets the Lego part code and color via the Lego part number.
        /// </summary>
        /// <param name="partNumber">A Lego part number.</param>
        /// <param name="partCode">A Lego part code.</param>
        /// <param name="color">A color name.</param>
        /// <returns>True of the part code and color could be retrieved.</returns>
        private bool GetPartCodeAndColor(string partNumber, out string partCode, out string color)
        {
            partCode = null;
            color = null;
            try
            {
                // Try to load XML file
                if (partAndColorCodesDocument == null)
                {
                    FileInfo file = new FileInfo(Assembly.GetExecutingAssembly().Location);
                    string fileName = Path.Combine(file.DirectoryName, "Xml\\PartAndColorCodes.xml");
                    if (File.Exists(fileName))
                    {
                        //TODO: Change to XML Serializer
                        partAndColorCodesDocument = new XmlDocument();
                        partAndColorCodesDocument.Load(fileName);
                    }
                }
                // Try to get part description.
                if (partAndColorCodesDocument != null)
                {
                    XmlNodeList nodes = partAndColorCodesDocument.SelectNodes("descendant::ITEM[CODENAME='" + partNumber + "']");
                    if (nodes.Count == 1 && nodes[0].ChildNodes.Count == 4)
                    {
                        partCode = nodes[0].ChildNodes[1].InnerText;
                        color = nodes[0].ChildNodes[2].InnerText;
                        return true;
                    }
                }
            }
            catch (XmlException)
            {
                HasErrors = true;
                ErrorMessage = "The Lego colors XML file isn't a valid XML file";
            }
            return false;
        }

        /// <summary>
        /// Gets the description for a part number from 
        /// the Lego parts XML file.
        /// </summary>
        /// <param name="partCode">A Lego part code.</param>
        /// <returns>A part description.</returns>
        private string GetPartDescription(string partCode)
        {
            try
            {
                // Try to load XML file
                if (partsDocument == null)
                {
                    FileInfo file = new FileInfo(Assembly.GetExecutingAssembly().Location);
                    string fileName = Path.Combine(file.DirectoryName, "Xml\\LegoParts.xml");
                    if (File.Exists(fileName))
                    {
                        //TODO: Change to XML Serializer
                        partsDocument = new XmlDocument();
                        partsDocument.Load(fileName);
                    }
                }
                // Try to get part description.
                if (partsDocument != null && !string.IsNullOrEmpty(partCode))
                {
                    XmlNodeList nodes = partsDocument.SelectNodes("descendant::ITEM[ITEMID='" + partCode + "']");
                    // Didn't find anything. Try to strip the color number from the part number.
                    if (nodes.Count == 0)
                    {
                        int length = partCode.Length - 2;
                        if (length > 0)
                        {
                            string number = partCode.Substring(0, length);
                            nodes = partsDocument.SelectNodes("descendant::ITEM[ITEMID='" + number + "']");
                        }
                    }
                    if (nodes.Count == 1 && nodes[0].ChildNodes.Count == 4)
                        return nodes[0].ChildNodes[2].InnerText;
                }
            }
            catch (XmlException)
            {
                HasErrors = true;
                ErrorMessage = "The Lego parts XML file isn't a valid XML file";
            }
            return null;
        }

        /// <summary>
        /// Extracts the LDD XML file from the Lego LDD file to
        /// the temp directory and overwrites any files with the
        /// same name.
        /// </summary>
        /// <param name="fileName">A file name</param>
        /// <param name="xmlFileName">The LDD XML file.</param>
        private void ExtractLddFile(string fileName)
        {
            string tempDirectory = Path.GetTempPath();

            // Extract Lego XML file.
            using (ZipFile zipFile = new ZipFile(fileName))
            {
                foreach (ZipEntry entry in zipFile)
                {
                    int index = entry.FileName.LastIndexOf('.');
                    string extention = entry.FileName.Substring(index + 1);

                    // Extract the first file with the extention LXFML.
                    if (string.IsNullOrEmpty(XmlFileName ) && 
                        string.Compare(extention, "lxfml", true) == 0)
                    {
                        entry.Extract(tempDirectory, ExtractExistingFileAction.OverwriteSilently);
                        XmlFileName = Path.Combine(tempDirectory, entry.FileName);
                    }
                    // Extract the first file with the extention LXFML.
                    if (string.IsNullOrEmpty(XmlFileName) && 
                        string.Compare(extention, "png", true) == 0)
                    {
                        entry.Extract(tempDirectory, ExtractExistingFileAction.OverwriteSilently);
                        ImageFileName = Path.Combine(tempDirectory, entry.FileName);
                    }
                }
            }

            HasErrors = string.IsNullOrEmpty(XmlFileName);
            if (HasErrors)
                ErrorMessage = "Couldn't extract LXFML from LDD file";
        }

       
        
        #endregion
    }
}
